package com.hfi.security.core.social.weixin.connect;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2Parameters;
import org.springframework.social.oauth2.OAuth2Template;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;

/**
 * 完成微信的OAuth2认证流程的模板类。
 * 国内厂商实现的OAuth2每个都不同, spring默认提供的OAuth2Template适应不了，只能针对每个厂商自己微调。
 * @author ChangLiang
 * @date 2019/8/23
 */
public class WeixinOAuth2Template extends OAuth2Template {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private String clientId;

    private String clientSecret;

    private String accessTokenUrl;

    public WeixinOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
        super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
        // useParametersForClientAuthentication=true，其才会带上client_id和client_secret
        setUseParametersForClientAuthentication(true);
        // 为了在exchangeForAccess方法中使用这三个参数 将其设置为成员变量
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.accessTokenUrl = accessTokenUrl;
    }

    /**
     * 构建发出去的请求 向请求中挂参数
     * 解析响应 处理额外的openId字段
     * @param authorizationCode
     * @param redirectUri
     * @param additionalParameters
     * @return
     */
    @Override
    public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri, MultiValueMap<String, String> additionalParameters) {
        StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);

        accessTokenRequestUrl.append("?appid="+clientId);
        accessTokenRequestUrl.append("&secret="+clientSecret);
        accessTokenRequestUrl.append("&code="+authorizationCode);
        accessTokenRequestUrl.append("&grant_type=authorization_code");
        accessTokenRequestUrl.append("&redirect_uri="+redirectUri);

        return getWeixinAccessGrant(accessTokenRequestUrl);
    }

    private AccessGrant getWeixinAccessGrant(StringBuilder accessTokenRequestUrl) {
        logger.info("获取微信access_token 请求url:{}", accessTokenRequestUrl);
        String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
        logger.info("获取微信access_token 响应内容:{}", response);

        // 由于其返回并不是标准的AccessGrant 多了openid字段
        // 这里先将其转换为Map
        Map<String,Object> result = null;
        try {
            result = new ObjectMapper().readValue(response, Map.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 返回内容包含错误码 直接返回空
        if (StringUtils.isNoneBlank(MapUtils.getString(result,"errcode"))) {
            String errcode = MapUtils.getString(result, "errcode");
            String errmsg = MapUtils.getString(result, "errmsg");
            throw new RuntimeException("获取access token失败, errcode:"+errcode+", errmsg:"+errmsg);
        }

        WeixinAccessGrant weixinAccessGrant = new WeixinAccessGrant(
                MapUtils.getString(result, "access_token"),
                MapUtils.getString(result, "scope"),
                MapUtils.getString(result, "refresh_token"),
                MapUtils.getLong(result, "expires_in")
        );

        weixinAccessGrant.setOpenId(MapUtils.getString(result, "openid"));

        return weixinAccessGrant;
    }

    /**
     * 构建第1步 将用户导向认证服务器（微信）的地址
     * @param parameters
     * @return
     */
    @Override
    public String buildAuthenticateUrl(OAuth2Parameters parameters) {
        String url = super.buildAuthorizeUrl(parameters);
        url = url + "&appid="+clientId+"&scope=snsapi_login";
        return url;
    }

    /**
     * 在绑定中使用了这个方法 与上面方法的区别:
     * 在ConnectSupport类中看 boolean useAuthenticateUrl = true
     * return oauthOperations.buildAuthenticateUrl(parameters);
     * 否则 return oauthOperations.buildAuthorizeUrl(parameters);
     * @param parameters
     * @return
     */
    @Override
    public String buildAuthorizeUrl(OAuth2Parameters parameters) {
        return buildAuthenticateUrl(parameters);
    }

    /**
     * 微信返回的contentType是html/text，添加相应的HttpMessageConverter来处理
     * @return
     */
    @Override
    protected RestTemplate createRestTemplate() {
        RestTemplate restTemplate = super.createRestTemplate();
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    }
}
